﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using sdd.subsystems.audio;
using sdd.subsystems.memory;
using sdd.subsystems.display;
using sdd.subsystems.ui;
using sdd.subsystems.ui.screens;

namespace sdd.apps
{
    public class AppTuner : IApplication
    {
        private System.Timers.Timer UIUpdateTimer = new System.Timers.Timer();
        private int _CurrentStationId = 0;

        public string Name { get { return "Tuner"; } }
        
        public Director Director { set; get; }

        public AudioStreamer Streamer { private set; get; }

        public ScrTuner TunerScreen { private set; get; }

        // station memory
        public ProgramMemory Memory { private set; get; }

        // the selected operational status
        public OperationalStatuses OperationalState { private set; get; }


        public AppTuner()
        {
            Memory = new ProgramMemory();

            Streamer = new AudioStreamer(2, 2); // 44k1 Stereo, 2 streams max
            Streamer.MetadataUpdated += _Streamer_OnMetadataUpdated;
            Streamer.StreamChanging += _Streamer_OnStreamChanging;
            Streamer.StreamChanged += _Streamer_OnStreamChanged;
            Streamer.StreamEnded += _Streamer_OnStreamEnded;
        }

        ~AppTuner()
        {
            Streamer.MetadataUpdated -= _Streamer_OnMetadataUpdated;
            Streamer.StreamChanging -= _Streamer_OnStreamChanging;
            Streamer.StreamChanged -= _Streamer_OnStreamChanged;
            Streamer.StreamEnded -= _Streamer_OnStreamEnded;    
        }

        
        public void Initialize()
        {
            Memory.Add(1, "http://yp.shoutcast.com/sbin/tunein-station.pls?id=38394");
            Memory.Add(2, "http://yp.shoutcast.com/sbin/tunein-station.pls?id=141842");
            Memory.Add(3, "http://yp.shoutcast.com/sbin/tunein-station.pls?id=251217");
            Memory.Add(4, "http://yp.shoutcast.com/sbin/tunein-station.pls?id=357541");
            Memory.Add(6, "http://yp.shoutcast.com/sbin/tunein-station.pls?id=116432");
            Memory.Add(7, "http://yp.shoutcast.com/sbin/tunein-station.pls?id=647391");
            Memory.Add(9, "http://yp.shoutcast.com/sbin/tunein-station.pls?id=457707");
            Memory.Add(10, "http://yp.shoutcast.com/sbin/tunein-station.pls?id=181612");
            Memory.Add(11, "http://yp.shoutcast.com/sbin/tunein-station.pls?id=99179385");
            Memory.Add(13, "http://www.google.com");
            Memory.Add(14, "http://mp3.radio.ch/radiozuerisee128k.m3u");
            Memory.Add(15, "http://mp3.radio.ch/radiobern164k.m3u");
            Memory.Add(20, "http://dir.xiph.org/listen/877727/listen.m3u"); // OPUS
            Memory.Add(21, "http://dir.xiph.org/listen/1410665/listen.m3u"); // OGG Vorbis
            //Memory.Add(22, "http://dir.xiph.org/listen/1401257/listen.m3u"); // OGG Theora - does not work

            TunerScreen = new ScrTuner(this);

            Director.ScreenManager.Register(TunerScreen);

            UIUpdateTimer.Elapsed += UIUpdateTimer_OnElapsed;
            UIUpdateTimer.Interval = 100;
            UIUpdateTimer.Start();

        }

        public void Terminate()
        {
            Director.ScreenManager.UnRegister(TunerScreen);
        }

        public void Activate()
        {
            TunerScreen.ProgramID = 1;
            CurrentStationId = Memory.First.Id;
            Director.ScreenManager.Show(TunerScreen);
            Play();            
        }

        public void Deactivate()
        {
            Stop();
        }
        
        
        // current station Id (number)
        public int CurrentStationId
        {
            set
            {
                if (_CurrentStationId != value)
                {
                    _CurrentStationId = value;
                    
                    TunerScreen.ProgramID = _CurrentStationId;
                }
            }
            get
            {
                return _CurrentStationId;
            }
        }

        // go to next available station
        public bool Next()
        {
            Station next = Memory.GetNext(CurrentStationId);
            int tryStationId = CurrentStationId;
            if (next != null)
            {
                switch (OperationalState)
                {
                    case OperationalStatuses.Play:
                        OperationalState = OperationalStatuses.Next;
                        while (Streamer.Start(next.Url, true) == null & OperationalState == OperationalStatuses.Next)
                        {
                            next = Memory.GetNext(next.Id);
                            System.Threading.Thread.Sleep(20);
                        }
                        return true;
                    case OperationalStatuses.Stop:
                        TunerScreen.StreamFormat = "";
                        TunerScreen.ArtistAndTitle = "";
                        CurrentStationId = next.Id;
                        return true;
                }
            }
            return false;
        }

        // go to previous available station
        public bool Previous()
        {
            Station prev = Memory.GetPrevious(CurrentStationId);
            if (prev != null)
            {
                switch (OperationalState)
                {
                    case OperationalStatuses.Play:
                        OperationalState = OperationalStatuses.Previous;
                        while (Streamer.Start(prev.Url, true) == null & OperationalState == OperationalStatuses.Previous)
                        {
                            prev = Memory.GetPrevious(prev.Id);
                            System.Threading.Thread.Sleep(20);
                        }
                        return true;
                    case OperationalStatuses.Stop:
                        TunerScreen.StreamFormat = "";
                        TunerScreen.ArtistAndTitle = "";
                        CurrentStationId = prev.Id;
                        return true;
                }
            }
            return false;
        }

        // start playback of current station
        public bool Play()
        {
            OperationalState = OperationalStatuses.Play;
            Station curStation = Memory.GetStation(CurrentStationId);
            TunerScreen.ProgramID = CurrentStationId;
            try
            {
                TunerScreen.ArtistAndTitle = "Connecting...";
                TunerScreen.StreamFormat = "";
                TunerScreen.StationName = "";
                Streamer.Start(Memory.GetStation(CurrentStationId).Url);
                return true;
            }
            catch (BASSException ex)
            {
                Stop();
                TunerScreen.ArtistAndTitle = "Station unavailable.";
                TunerScreen.StreamFormat = string.Format("{0} ERROR.", ex.BassErrorText.Replace("BASS_ERROR_", ""));
                return false;
            }
        }

        // stop all playback
        public bool Stop()
        {
            OperationalState = OperationalStatuses.Stop;
            Streamer.StopAll(true);
            TunerScreen.ArtistAndTitle = "Stopped.";
            TunerScreen.StationName = "";
            TunerScreen.StreamFormat = "";
            return true;
        }




        
        // AudioSteamer events
        void _Streamer_OnMetadataUpdated(object sender, EventArgs e)
        {
            AudioStreamer streamer = (AudioStreamer)sender;
            TunerScreen.ArtistAndTitle = streamer.FormattedSongLine;
        }

        void _Streamer_OnStreamChanging(object sender, AudioStreamer.StreamChangingEventArgs e)
        {
            TunerScreen.BlinkProgramId = true;
        }

        void _Streamer_OnStreamChanged(object sender, AudioStreamer.StreamChangedEventArgs e)
        {
            AudioStreamer streamer = (AudioStreamer)sender;
            InetStream stream = streamer.CurrentStream;

            TunerScreen.BlinkProgramId = false;
            
            var progId = Memory.GetStation(stream.URL);
            if (progId != null) { TunerScreen.ProgramID = progId.Id; CurrentStationId = progId.Id; }

            TunerScreen.ArtistAndTitle = "";
            TunerScreen.StationName = stream.Station;
            TunerScreen.StreamFormat = ((stream.Bitrate != 0) ? stream.Bitrate + "kbps " : "") + GetChannelFormatString(stream.NumberOfChannels) + " " + streamer.StreamFormatString + " (" + stream.BitsPerSample + "bit@" + streamer.SamplerateString + ")";

            if (stream != null) { OperationalState = OperationalStatuses.Play; } else { OperationalState = OperationalStatuses.Stop; }
            

        }

        void _Streamer_OnStreamEnded(object sender, EventArgs e)
        {
            AudioStreamer streamer = (AudioStreamer)sender;
            InetStream stream = streamer.CurrentStream;

            TunerScreen.BlinkProgramId = false;

            // disable stream indicator led
            Director.Display.SetLedBlinker(LedLocations.Bottom, false);
            Director.Display.SetLedIndicators(LedLocations.Bottom, LedColors.Dark);
            TunerScreen.StationName = "";
            TunerScreen.StreamFormat = "";
            TunerScreen.ArtistAndTitle = "";

            if (OperationalState == OperationalStatuses.Play)
            {
                // TODO: restart count per station, don't restart after N tries
                // we are on PLAY, so restart the stream
                if (stream != null) { streamer.Start(stream.URL); }
            }

        }

        // UI update timer
        void UIUpdateTimer_OnElapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            // update all physical UI elements (leds, display brightness etc)
            UpdateStreamIndicator();
        }

        // UI update worker function
        void UpdateStreamIndicator()
        {
            if (Streamer.CurrentStream != null)
            {
                // update stream indicator led
                int LevelBuffer = Streamer.CurrentStream.BufferLevel;

                if (LevelBuffer == 0)
                {
                    // buffer is empty - very bad
                    Director.Display.SetLedIndicators(LedLocations.Bottom, LedColors.Red);
                    Director.Display.SetLedBlinker(LedLocations.Bottom, true);
                }
                else if (LevelBuffer < 17)
                {
                    // buffer is low - not good
                    Director.Display.SetLedIndicators(LedLocations.Bottom, LedColors.Red);
                    Director.Display.SetLedBlinker(LedLocations.Bottom, false);
                }
                else if (LevelBuffer < 45 & LevelBuffer > 25)
                {
                    // buffer is moderately filled - okay
                    Director.Display.SetLedIndicators(LedLocations.Bottom, LedColors.Amber);
                    Director.Display.SetLedBlinker(LedLocations.Bottom, false);
                }
                else if (LevelBuffer > 60)
                {
                    // buffer has at least 6 seconds of audio - good
                    Director.Display.SetLedIndicators(LedLocations.Bottom, LedColors.Green);
                    Director.Display.SetLedBlinker(LedLocations.Bottom, false);
                }
            }
            else
            {
                // dark led == there is no stream
                Director.Display.SetLedIndicators(LedLocations.Bottom, LedColors.Dark);
                Director.Display.SetLedBlinker(LedLocations.Bottom, false);
            }
        }

        // get channel format name from number of channels
        static string GetChannelFormatString(int channels)
        {
            string fStr;
            switch (channels)
            {
                case 0:
                    fStr = "N/A";
                    break;
                case 1:
                    fStr = "MONO";
                    break;
                case 2:
                    fStr = "STEREO";
                    break;
                case 3:
                    fStr = "2.1";
                    break;
                case 4:
                    fStr = "QUADRO";
                    break;
                case 6:
                    fStr = "5.1";
                    break;
                case 8:
                    fStr = "7.1";
                    break;
                case 9:
                    fStr = "8.1";
                    break;
                default:
                    fStr = channels + "Ch";
                    break;
            }
            return fStr;
        }


        // operational states of the Tuner
        public enum OperationalStatuses
        {
            Stop = 0,
            Play = 1,
            Next,
            Previous
        }






    }
    
}
